/**
 * This file is a part of qloudgen-sec. 
 * You can redistribute qloudgen-sec and/or modify it under the terms of the Lesser GNU General Public License version 3. 
 * qloudgen-sec is distributed WITHOUT ANY WARRANTY. 
 *
 * See the Lesser GNU General Public License for more details at http://www.gnu.org/licenses/. 
 **/
package org.qloudgen.sec.jetty.nosql;

import java.util.Map;
import java.util.List;
import java.util.Collection;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

import org.qloudgen.sec.cache.Cache;
import org.qloudgen.sec.cache.CacheManager;
import org.qloudgen.sec.cache.redisson.ClusterCacheImpl;

/**
 * <p>Title: Cached session id support</p>
 * <p>Description: Multiple map for cached session id manager for jetty cluster implementation.</p>
 * <p>Copyright: qloudgen/sec 2014</p>
 * @author <a href="mailto:hyq.dd.sun@gmail.com">glue.3M</a>
 */
public class RedisClusterCachedSessionIdManager extends CachedSessionIdManager {
	/**
	 * Log output.
	 */
	private final static Logger logger = Log.getLogger( RedisClusterCachedSessionIdManager.class );
	/**
	 * Default max records count.
	 */
	private long count = 100;
	
	/**
	 * Custom constructor.
	 *
	 * @param server
	 * @param cacheManager
	 */
	public RedisClusterCachedSessionIdManager(Server server , CacheManager cacheManager) {
		super( server , cacheManager );
	}
	
	/**
	 * Custom constructor.
	 *
	 * @param server
	 * @param cacheManager
	 * @param cacheName
	 */
	public RedisClusterCachedSessionIdManager(Server server , CacheManager cacheManager , String cacheName) {
		super( server , cacheManager , cacheName );
	}
	
	/**
	 * Sets current max records count.
	 *
	 * @param count
	 */
	public void setCount(final long count) {
		this.count = count;
	}
	
	/**
	 * ScavengeFully will expire all sessions.
	 * In most circumstances you should never need to call this method.
	 * Only for jetty managed operation.
	 */
	@Override
	protected void scavengeFully() {
		int n = 0;
		int c = 0;
		while ( true ) {
			List<String> ids = getSessionIds( n , c , "*" , count );
			if ( ids == null ) {
				break;
			}
			//
			c = Integer.parseInt( ids.get( 0 ) );
			logger.info( "Current scavenge session status...[{},{},{}]" , n , c , ids.size() - 1 );
			//
			if ( ids.size() > 1 ) {
				/**
				 * Loop current all sessions.
				 */
		    	for ( int i = 1 ; i < ids.size() ; i++ ) {
		    		expireAll( ids.get( i ) );
		    	}
		    }
		    //
			if ( c == 0 ) {
				n = n + 1;
			}
		}
	}
	
	/**
	 * Purge is a process that cleans the cache cluster of old sessions that are no longer valid.
	 * Only for jetty managed operation.
	 */
	@Override
	protected void purgeFully() {
		int n = 0;
		int c = 0;
		while ( true ) {
			List<String> ids = getSessionIds( n , c , "*" , count );
			if ( ids == null ) {
				break;
			}
			//
			c = Integer.parseInt( ids.get( 0 ) );
			logger.info( "Current scavenge session status...[{},{},{}]" , n , c , ids.size() - 1 );
			//
			if ( ids.size() > 1 ) {
				/**
				 * Loop current all sessions.
				 */
		    	for ( int i = 1 ; i < ids.size() ; i++ ) {
		    		String sessionId = ids.get( i );
					Map session = getSession( sessionId );
					logger.debug( "Current purge sessions is...{}" , session );
					if ( session != null ) {
						//
						boolean valid = ( Boolean )session.get( AbstractCachedSessionManager.__VALID );
						if ( ! valid ) {
							logger.debug( "Current purge invalid session is...{}" , sessionId );
							removeSession( sessionId );
						}
					}
		    	}
		    }
		    //
			if ( c == 0 ) {
				n = n + 1;
			}
		}
	}
	
	/**
	 * Purge is a process that cleans the cache cluster of old sessions that are no longer valid.
	 * 
	 * There are two checks being done here:
	 * 
	 *  - if the accessed time is older than the current time minus the purge invalid age
	 *    and it is no longer valid then remove that session
	 *  - if the accessed time is older then the current time minus the purge valid age
	 *    then we consider this a lost record and remove it
	 *    
	 *  NOTE: if your system supports long lived sessions then the purge valid age should be
	 *  set to zero so the check is skipped.
	 *  
	 *  The second check was added to catch sessions that were being managed on machines 
	 *  that might have crashed without marking their sessions as 'valid=false'
	 */
	@Override
	protected void purge() {
		int n = 0;
		int c = 0;
		while ( true ) {
			List<String> ids = getSessionIds( n , c , "*" , count );
			if ( ids == null ) {
				break;
			}
			//
			c = Integer.parseInt( ids.get( 0 ) );
			logger.info( "Current scavenge session status...[{},{},{}]" , n , c , ids.size() - 1 );
			//
			if ( ids.size() > 1 ) {
				/**
				 * Loop current all sessions.
				 */
		    	for ( int i = 1 ; i < ids.size() ; i++ ) {
		    		String sessionId = ids.get( i );
					Map session = getSession( sessionId );
					logger.debug( "Current purge sessions is...{}" , session );
					if ( session != null ) {
						//
						long accessed = ( Long )session.get( AbstractCachedSessionManager.__ACCESSED );
						boolean valid = ( Boolean )session.get( AbstractCachedSessionManager.__VALID );
						/**
						 *
						 */
						long invalidAge = System.currentTimeMillis() - _purgeInvalidAge;
						logger.debug( "Current purge session invalid age is...{}" , invalidAge );
						//
						if ( accessed < invalidAge && ! valid ) {
							logger.debug( "Current purge invalid session is...{}" , sessionId );
							removeSession( sessionId );
						}
						/**
						 *
						 */
						if ( _purgeValidAge != 0 ) {
							long validAge = System.currentTimeMillis() - _purgeValidAge;
							logger.debug( "Current purge session valid age is...{}" , validAge );
							//
							if ( accessed < validAge && valid ) {
								logger.debug( "Current purge valid session is...{}" , sessionId );
								removeSession( sessionId );
							}
						}
					}
		    	}
		    }
		    //
			if ( c == 0 ) {
				n = n + 1;
			}
		}
	}
	
	/**
	 *
	 *
	 * @return List<String>
	 */
	protected List<String> getSessionIds(int num , long cursor , String pattern , long count) {
		ClusterCacheImpl<String, Map> _sessions = ( ClusterCacheImpl )cacheManager.getCache( cacheName );
		return _sessions.keys( num , cursor , pattern , count );
	}
	
	/**
	 * Returns all session.
	 *
	 * @return Collection
	 */
	@Override
	protected Collection<String> getSessionIds() {
		throw new RuntimeException( "Redis cluster will not support map.keys operation." );
	}
}